﻿using System.Net;

namespace anydata.Models
{
    public class ProxyContext
    {
        private readonly HttpClient _httpClient;
        private const int StreamCopyBufferSize = 81920;
        internal ProxyContext(
            HttpContext httpContext,
            HttpRequestMessage upstreamRequest,
            HttpClient httpClient)
        {
            _httpClient = httpClient;
            HttpContext = httpContext;
            UpstreamRequest = upstreamRequest;
        }

        public HttpContext HttpContext { get; }

        public HttpRequestMessage UpstreamRequest { get; }

        public async Task Send()
        {
            var response = new HttpResponseMessage(HttpStatusCode.BadRequest);
            try
            {
                response = await _httpClient
                    .SendAsync(
                        UpstreamRequest,
                        HttpCompletionOption.ResponseHeadersRead,
                        HttpContext.RequestAborted)
                    .ConfigureAwait(false);
            }
            catch (TaskCanceledException ex) when (ex.InnerException is IOException)
            {
                response = new HttpResponseMessage(HttpStatusCode.GatewayTimeout);
            }
            catch (OperationCanceledException)
            {
                response = new HttpResponseMessage(HttpStatusCode.BadGateway);
            }
            catch (HttpRequestException)
            {
                response = new HttpResponseMessage(HttpStatusCode.BadGateway);
            }
            await ProxyResponse(response);
        }

        private async Task ProxyResponse(HttpResponseMessage responseMessage)
        {
            var response = HttpContext.Response;

            response.StatusCode = (int)responseMessage.StatusCode;
            foreach (var header in responseMessage.Headers)
            {
                response.Headers[header.Key] = header.Value.ToArray();
            }

            if (responseMessage.Content != null)
            {
                foreach (var header in responseMessage.Content.Headers)
                {
                    response.Headers[header.Key] = header.Value.ToArray();
                }
            }

            if (responseMessage.Content != null)
            {
                using (var responseStream = await responseMessage
                    .Content
                    .ReadAsStreamAsync()
                    .ConfigureAwait(false))
                {
                    try
                    {
                        await responseStream
                            .CopyToAsync(response.Body, StreamCopyBufferSize, HttpContext.RequestAborted)
                            .ConfigureAwait(false);
                        if (responseStream.CanWrite)
                        {
                            await responseStream
                                .FlushAsync(HttpContext.RequestAborted)
                                .ConfigureAwait(false);
                        }
                    }
                    catch (IOException)
                    {
                        // Usually a client abort. Ignore.
                    }
                }
            }
        }
    }
}
